//
//  StringStyle+Part.swift
//  BonMot
//
//  Created by Brian King on 9/1/16.
//  Copyright © 2016 Rightpoint. All rights reserved.
//

#if os(OSX)
    import AppKit
#else
    import UIKit
#endif

extension StringStyle {

    /// Each `Part` encapsulates one setting in a `StringStyle`. It is used
    /// in a DSL for building `StringStyle` across BonMot.
    public enum Part {

        case extraAttributes(StyleAttributes)
        case font(BONFont)
        case link(URL)
        case backgroundColor(BONColor)
        case color(BONColor)
        case underline(NSUnderlineStyle, BONColor?)
        case strikethrough(NSUnderlineStyle, BONColor?)
        case baselineOffset(CGFloat)

        #if os(iOS) || os(tvOS) || os(watchOS)

        /// If set to `true`, when the string is read aloud, all punctuation will
        /// be spoken aloud as well.
        case speaksPunctuation(Bool)

        /// The BCP 47 language code that you would like the system to use when
        /// reading this string aloud. A BCP 47 language code is generally of
        /// the form “language-REGION”, e.g. “en-US”. You can see a [list of
        /// languages and regions](http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
        /// and learn more about [BCP 47](https://www.rfc-editor.org/rfc/rfc5646.txt).
        case speakingLanguage(String)

        /// The pitch of the voice used to read the text aloud. The range is
        /// 0 to 2, where 0 is the lowest, 2 is the highest, and 1 is the default.
        case speakingPitch(Double)

        /// The IPA pronunciation of the given range.
        case speakingPronunciation(String)

        /// Whether the spoken text is queued behind, or interrupts, existing spoken content.
        case shouldQueueSpeechAnnouncement(Bool)

        /// The accessibility heading level of the text.
        case headingLevel(HeadingLevel)
        #endif

        case ligatures(Ligatures)

        case alignment(NSTextAlignment)
        case tracking(Tracking)
        case lineSpacing(CGFloat)
        case paragraphSpacingAfter(CGFloat)
        case firstLineHeadIndent(CGFloat)
        case headIndent(CGFloat)
        case tailIndent(CGFloat)
        case lineBreakMode(NSLineBreakMode)
        case minimumLineHeight(CGFloat)
        case maximumLineHeight(CGFloat)
        case baseWritingDirection(NSWritingDirection)
        case lineHeightMultiple(CGFloat)
        case paragraphSpacingBefore(CGFloat)
        case allowsDefaultTighteningForTruncation(Bool)

        /// Values from 0 to 1 will result in varying levels of hyphenation,
        /// with higher values resulting in more aggressive (i.e. more frequent)
        /// hyphenation.
        ///
        /// Hyphenation is attempted when the ratio of the text width (as broken
        /// without hyphenation) to the width of the line fragment is less than
        /// the hyphenation factor. When the paragraph’s hyphenation factor is
        /// 0.0, the layout manager’s hyphenation factor is used instead. When
        /// both are 0.0, hyphenation is disabled.
        case hyphenationFactor(Float)

        case xml
        case xmlRules([XMLStyleRule])
        case xmlStyler(XMLStyler)
        case transform(Transform)
        #if os(iOS) || os(tvOS) || os(OSX)
        case fontFeature(FontFeatureProvider)

        case numberSpacing(NumberSpacing)
        case numberCase(NumberCase)
        case fractions(Fractions)

        case superscript(Bool)
        case `subscript`(Bool)
        case ordinals(Bool)
        case scientificInferiors(Bool)

        case smallCaps(SmallCaps)

        case stylisticAlternates(StylisticAlternates)
        case contextualAlternates(ContextualAlternates)
        #endif
        #if os(iOS) || os(tvOS)
        case textStyle(BonMotTextStyle)
        #endif
        #if os(iOS) || os(tvOS)
        case adapt(AdaptiveStyle)
        #endif

        case emphasis(Emphasis)

        // An advanced part that allows combining multiple parts as a single part
        case style(StringStyle)

    }

    /// Create a `StringStyle` from zero or more `Part`s.
    ///
    /// - Parameter parts: Zero or more `Part`s
    public init(_ parts: Part...) {
        self.init(parts)
    }

    /// Create a `StringStyle` from an array of parts
    ///
    /// - Parameter parts: An array of `StylePart`s
    public init(_ parts: [Part]) {
        self.init()
        for part in parts {
            update(part: part)
        }
    }

    /// Derive a new `StringStyle` based on this style, updated with zero or
    /// more `Part`s.
    ///
    /// - Parameter parts: Zero or more `Part`s
    /// - Returns: A newly configured `StringStyle`
    public func byAdding(_ parts: Part...) -> StringStyle {
        return byAdding(parts)
    }

    /// Derive a new `StringStyle` based on this style, updated with zero or
    /// more `Part`s.
    ///
    /// - Parameter parts: an array of `Part`s
    /// - Returns: A newly configured `StringStyle`
    public func byAdding(_ parts: [Part]) -> StringStyle {
        var style = self
        for part in parts {
            style.update(part: part)
        }
        return style
    }

    // swiftlint:disable function_body_length
    // swiftlint:disable cyclomatic_complexity
    /// Update the style with the specified style part.
    ///
    /// - Parameter stylePart: The style part with which to update the receiver.
    mutating func update(part stylePart: Part) {
        switch stylePart {
        case let .extraAttributes(attributes):
            self.extraAttributes = attributes
        case let .font(font):
            self.font = font
        case let .link(link):
            self.link = link
        case let .backgroundColor(backgroundColor):
            self.backgroundColor = backgroundColor
        case let .color(color):
            self.color = color
        case let .underline(style, color):
            self.underline = (style, color)
        case let .strikethrough(style, color):
            self.strikethrough = (style, color)
        case let .baselineOffset(baselineOffset):
            self.baselineOffset = baselineOffset
        case let .ligatures(ligatures):
            self.ligatures = ligatures
        case let .alignment(alignment):
            self.alignment = alignment
        case let .tracking(tracking):
            self.tracking = tracking
        case let .lineSpacing(lineSpacing):
            self.lineSpacing = lineSpacing
        case let .paragraphSpacingAfter(paragraphSpacingAfter):
            self.paragraphSpacingAfter = paragraphSpacingAfter
        case let .firstLineHeadIndent(firstLineHeadIndent):
            self.firstLineHeadIndent = firstLineHeadIndent
        case let .headIndent(headIndent):
            self.headIndent = headIndent
        case let .tailIndent(tailIndent):
            self.tailIndent = tailIndent
        case let .lineBreakMode(lineBreakMode):
            self.lineBreakMode = lineBreakMode
        case let .minimumLineHeight(minimumLineHeight):
            self.minimumLineHeight = minimumLineHeight
        case let .maximumLineHeight(maximumLineHeight):
            self.maximumLineHeight = maximumLineHeight
        case let .baseWritingDirection(baseWritingDirection):
            self.baseWritingDirection = baseWritingDirection
        case let .lineHeightMultiple(lineHeightMultiple):
            self.lineHeightMultiple = lineHeightMultiple
        case let .paragraphSpacingBefore(paragraphSpacingBefore):
            self.paragraphSpacingBefore = paragraphSpacingBefore
        case .xml:
            self.xmlStyler = NSAttributedString.defaultXMLStyler
        case var .xmlRules(rules):
            rules.append(contentsOf: Special.insertionRules)
            self.xmlStyler = XMLStyleRule.Styler(rules: rules)
        case let .xmlStyler(xmlStyler):
            self.xmlStyler = xmlStyler
        case let .transform(transform):
            self.transform = transform
        case let .style(style):
            self.add(stringStyle: style)
        case let .emphasis(emphasis):
            self.emphasis = emphasis
        case let .hyphenationFactor(hyphenationFactor):
            self.hyphenationFactor = hyphenationFactor
        case let .allowsDefaultTighteningForTruncation(allowsDefaultTighteningForTruncation):
            self.allowsDefaultTighteningForTruncation = allowsDefaultTighteningForTruncation
#if os(iOS) || os(tvOS) || os(watchOS)
        case let .speaksPunctuation(speaksPunctuation):
            self.speaksPunctuation = speaksPunctuation
        case let .speakingLanguage(speakingLanguage):
            self.speakingLanguage = speakingLanguage
        case let .speakingPitch(speakingPitch):
            self.speakingPitch = speakingPitch
        case let .speakingPronunciation(speakingPronunciation):
            self.speakingPronunciation = speakingPronunciation
        case let .shouldQueueSpeechAnnouncement(shouldQueueSpeechAnnouncement):
            self.shouldQueueSpeechAnnouncement = shouldQueueSpeechAnnouncement
        case let .headingLevel(headingLevel):
            self.headingLevel = headingLevel
#endif
#if os(OSX) || os(iOS) || os(tvOS)
        case let .numberCase(numberCase):
            self.numberCase = numberCase
        case let .numberSpacing(numberSpacing):
            self.numberSpacing = numberSpacing
        case let .fractions(fractions):
            self.fractions = fractions
        case let .superscript(superscript):
            self.superscript = superscript
        case let .`subscript`(`subscript`):
            self.`subscript` = `subscript`
        case let .ordinals(ordinals):
            self.ordinals = ordinals
        case let .scientificInferiors(scientificInferiors):
            self.scientificInferiors = scientificInferiors
        case let .smallCaps(smallCaps):
            self.smallCaps.insert(smallCaps)
        case let .stylisticAlternates(stylisticAlternates):
            self.stylisticAlternates.add(other: stylisticAlternates)
        case let .contextualAlternates(contextualAlternates):
            self.contextualAlternates.add(other: contextualAlternates)
        case let .fontFeature(featureProvider):
            self.fontFeatureProviders.append(featureProvider)
#endif
#if os(iOS) || os(tvOS)
        case let .adapt(style):
            self.adaptations.append(style)
        case let .textStyle(textStyle):
            self.font = UIFont.bon_preferredFont(forTextStyle: textStyle, compatibleWith: nil)
#endif
        }
    }
    // swiftlint:enable function_body_length
    // swiftlint:enable cyclomatic_complexity

}
